#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <stdlib.h>

/**
 * -l：除文件名称外，还要列出文件型态/权限/硬连接数目/拥有 者/大小
 * -d：只显示目录，而不显示其下的文件，不递归，即使有-R也不递归
 * -R：同时列出所有子目录层
 * -a：列出目录下所有文件，包括以 . 开头的隐含文件
 * -i：列出每个文件的 inode 号
 */

int lFLAG = 0;
int dFLAG = 0;
int RFLAG = 0;
int aFLAG = 0;
int iFLAG = 0;


// ----------- 函数定义 ------------

// 读取文件夹，包括隐含文件，根据AFLAG判断是否打印
void traverseDir(DIR *dirp, char *dirPath) {
    struct dirent *direntp;
    int len = strlen(dirPath);
    DIR *subDirPtrs[100];
    char subPaths[100][100];
    int ind = 0;
    
    if (RFLAG) {
        printf("%s:\n", dirPath);
    }

    while ((direntp = readdir(dirp)) != NULL) {
        // 对于每个参数 dirPath，先将其下的所有文件遍历一遍
        concatSubDir(dirPath, direntp->d_name);

        if (dFLAG) {
            // 只显示根文件夹 .
            printls(direntp, dirPath);
            return;
        }

        if (direntp->d_name[0] == '.' && !aFLAG) {
            // 如果未要求打印隐含文件，且当前遍历到了隐含文件
            // 去除后继续循环
            removeSubDir(dirPath, len);
            continue;
        }

        printls(direntp, dirPath);

        if (RFLAG) {
            if (strlen(direntp->d_name) == 1 && direntp->d_name[0] == '.'
            || strlen(direntp->d_name) == 2 && direntp->d_name[0] == '.' && direntp->d_name[1] == '.') {
                // 当前是隐含文件 . 和 .. 则直接继续循环
                removeSubDir(dirPath, len);
                continue;
            }

            if (direntp->d_type == DT_DIR) {
                // 目录文件
                DIR *nextDirp;
                if ((nextDirp = opendir(dirPath)) != NULL) {
                    // 把子文件夹名称和文件指针暂存，便于-R参数后续的遍历
                    memcpy(subPaths[ind], dirPath, strlen(dirPath));
                    subDirPtrs[ind++] = nextDirp;
                }
            }
        }
        removeSubDir(dirPath, len);
    }

    // 若有 -R，则需要递归地调用暂存的所有子文件夹
    if (RFLAG) {
        for (int i = 0; i < ind; i++) {
            traverseDir(subDirPtrs[i], subPaths[i]);
        }
    }

    closedir(dirp);
}

// 添加路径，以访问上级目录的子目录
void concatSubDir(char *dirPath, char *subDirPath) {
    dirPath[strlen(dirPath)] = '/';
    memcpy(dirPath + strlen(dirPath), subDirPath, strlen(subDirPath));
}

// 删除路径，以回到上级目录
// dirLen: 父目录的字符串长度
void removeSubDir(char *dirPath, int dirLen) {
    memset(dirPath + dirLen, '\0', strlen(dirPath) - dirLen);
}

// 打印关键信息
void printls(struct dirent *direntp, char *dirPath) {
    // 如有 -i 参数，先打印 inode 号
    if (iFLAG) {
        printf("%ld ", direntp->d_ino);
    }
    // 如有 -l 参数，依次打印文件类型/权限/user/grp/字节大小/最后修改时间/文件名
    if (lFLAG) {
        struct stat st;
        char typeAndPermission[11];
        if (stat(dirPath, &st) < 0) {
            perror("Open file failed\n");
        } else {
            fileTypeAndPermission(st.st_mode, typeAndPermission);
            printf("%s ", typeAndPermission);       // 类型和权限
            printf("%5d ", (int) st.st_nlink);     // 硬链接数
            printf("%6s ", uid2Name(st.st_uid));   // 用户名
            printf("%6s ", gid2Name(st.st_gid));   // 组名
            printf("%-6ld ", (long) st.st_size);    // 字节大小
            printf("%s ", ctime(&(st.st_mtime)));// 最后一次修改时间
        }
    }
    // 打印文件名
    printf("%s\n", direntp->d_name);
}

// 获取文件类型及文件权限
// 前一位：文件类型，文件夹d，普通文件-
// 后九位：文件权限，owner、grp、other (rwx)
void fileTypeAndPermission(int mode, char str[]) {
    int fileType = mode & S_IFMT;  // 取文件类型
    strcpy(str, "----------");

    switch (fileType) {
        case S_IFDIR:
            str[0] = 'd';
            break;
        case S_IFBLK:
            str[0] = 'b';
            break;
        case S_IFCHR:
            str[0] = 'c';
            break;
        default:
            break;
    }
    
    if (mode & S_IREAD) {
        str[1] = 'r';
    }
    if (mode & S_IWRITE) {
        str[2] = 'w';
    }
    if (mode & S_IEXEC) {
        str[3] = 'x';
    }
    if (mode & S_IRGRP) {
        str[4] = 'r';
    }
    if (mode & S_IWGRP) {
        str[5] = 'w';
    }
    if (mode & S_IXGRP) {
        str[6] = 'x';
    }
    if (mode & S_IROTH) {
        str[7] = 'r';
    }
    if (mode & S_IWOTH) {
        str[8] = 'w';
    }
    if (mode & S_IXOTH) {
        str[9] = 'x';
    }
}

// 根据stat中的st_uid获取用户名
char *uid2Name(uid_t uid) {
    struct passwd *pwd;
    pwd = getpwuid(uid);
    return pwd->pw_name;
}

// 根据stat中的st_gid获取组名
char *gid2Name(gid_t gid) {
    struct group *grp;
    grp = getgrgid(gid);
    return grp->gr_name;
}

// ls函数主体
void ls(char *dirName) {
    DIR *dirp;
    // 获取当前工作目录
    char *dirPath = getenv("PWD");

    if ((dirp = opendir(dirName)) == NULL) {
        perror("ls: 不能打开文件夹\n");
    } else {
        char root[1000];
        memset(root, '\0', sizeof(root));
        strcpy(root, dirPath);
        traverseDir(dirp, root);
    }
}



int main(int argc, char *argv[]) {
    // 提取参数
    for (int i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "-l") == 0) {
            lFLAG = 1;
        }
        else if (strcmp(argv[i], "-d") == 0) {
            dFLAG = 1;
        }
        else if (strcmp(argv[i], "-R") == 0) {
            RFLAG = 1;
        }
        else if (strcmp(argv[i], "-a") == 0) {
            aFLAG = 1;
        }
        else if (strcmp(argv[i], "-i") == 0) {
            iFLAG = 1;
        }
        else {
            printf("%s: bad argument.\n", argv[i]);
        }
    }
    ls(".");

    return 0;
}
